/*
 * Lenovo Group
 * Copyright (c) 2015-2016 All Rights Reserved.
 * 
 * Project Name:itsm-gateway
 * Create Time: 2016年1月15日 下午3:02:56
 */
package com.lenovo.lmrp.server.itsmgateway.adapter.ms;

import java.io.InputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URL;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import javax.inject.Named;
import javax.xml.namespace.QName;
import javax.xml.ws.Endpoint;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlFactory;
import com.google.inject.Inject;
import com.lenovo.lmrp.api.constant.TicketAction;
import com.lenovo.lmrp.api.model.entity.AddressPO;
import com.lenovo.lmrp.api.model.entity.AttachmentPO;
import com.lenovo.lmrp.api.model.entity.EngineerPO;
import com.lenovo.lmrp.api.model.entity.EngineerPO.EngineerStatus;
import com.lenovo.lmrp.api.model.entity.TicketExtensePO;
import com.lenovo.lmrp.api.model.entity.TicketPO;
import com.lenovo.lmrp.api.model.view.ChangedTicketVO;
import com.lenovo.lmrp.api.model.view.EngineerVO;
import com.lenovo.lmrp.api.model.view.PartsVO;
import com.lenovo.lmrp.api.model.view.TicketVO;
import com.lenovo.lmrp.base.IFileService;
import com.lenovo.lmrp.base.LowerCaseMap;
import com.lenovo.lmrp.base.ParamNameDef;
import com.lenovo.lmrp.server.itsmgateway.adapter.AItsmAdapter;
import com.lenovo.lmrp.server.itsmgateway.domain.metadata.ItsmEntityTranslator.EntityName;
import com.lenovo.lmrp.server.itsmgateway.repository.ARepositoryFactory;
import com.lenovo.sdc.framework.cache.ACacheClient;
import com.lenovo.sdc.framework.cache.CacheFactory;
import com.lenovo.sdc.framework.cache.CacheItem;
import com.lenovo.sdc.framework.dao.ADaoFactory;
import com.lenovo.sdc.framework.dao.ICommonDAO;
import com.lenovo.sdc.framework.dao.ICommonDAO.FinderEnum;
import com.lenovo.sdc.framework.mq.AMQClient;
import com.lenovo.sdc.framework.scheduler.ScheduleTaskManager;
import com.lenovo.sdc.framework.util.AttributeBean;
import com.lenovo.sdc.framework.util.CommonUtil;
import com.lenovo.sdc.framework.util.Tuple;
import com.lenovo.sdc.framework.util.Tuple.Pair;
import com.lenovo.sdc.framework.util.Tuple.Triplet;
import com.lenovo.sdc.framework.util.exception.ErrorCode;
import com.lenovo.sdc.framework.util.exception.TraceableException;

/**
 * 
 * @author wujin
 * @version v1.0
 */
@Named("lenovo-ms")
public class LenovoMSAdapter extends AItsmAdapter {
    private static final Logger logger = LoggerFactory.getLogger(LenovoMSAdapter.class);

    protected static final String REMEDY_FIELD_STATUS = "status";
    protected static final String REMEDY_FIELD_CONTENT = "content";
    protected static final String PARAM_SYNC_SERVICE_ADDR = "lmrp.webservice.address";
    protected static final String PARAM_REMEDY_SERVICE_ADDR = "remedy.webservice.address";
    protected static final String TICKET_LIST_CACHE_KEY = "TICKET_LIST";

    protected static enum ResponseCode {
        OK, ERROR, EXCEPTION
    }

    private static final QName REMEDY_SERVICE_NAME = new QName("http://services.lenovo.com/", "RemedyService");
    
    /** used for send out ticket event */
    private AMQClient mqclient;
    
    /** used for cache send failed ticket request to retry */
    private ACacheClient cacher;
    
    /** used for parse xml message */
    private ObjectMapper mapper;

    /** used for read/write attachment file */
    private IFileService fileService;
    
    /** client with Remedy Service */
    private RemedyService remedyService;

    /** handler for process received message from Remedy */
    private DefaultRemedySyncHandler syncHandler;
    
    /** used for import base data */
    private BaseDataImporter importer;

    @Inject
    public void set(IFileService fileService) {
        this.fileService = fileService;
        syncHandler.set(fileService);
    }

    @Inject
    public void set(AMQClient mqclient) {
        this.mqclient = mqclient;
        syncHandler.set(mqclient);
    }

    protected LenovoMSAdapter() {
        super(new AttributeBean());
    }

    /**
     * @param params
     */
    public LenovoMSAdapter(AttributeBean params) {
        super(params);

        // create Remedy Sync data handler that convert Remdy data to ourself
        // data format
        syncHandler = new DefaultRemedySyncHandler(params);

        // publish SOAP service to receive XiHe pushed data,
        // for example ticket, engineer, attachment, client, provider, etc.
        Object implementor = new LenovoAppWsProcessRunServiceImpl(syncHandler);
        String address = params.getAttribute(PARAM_SYNC_SERVICE_ADDR,
            "http://" + getAvaliableIP() + ":8080/service/LenovoAppInterFace/ItsmProt?wsdl");
        Endpoint.publish(address, implementor);

        // create SOAP client to connect XiHe SOAP service, used for upload
        // changed data
        try {
            address = params.getAttribute(PARAM_REMEDY_SERVICE_ADDR,
                "http://10.99.236.248:8080/LenovoAppInterFace/RemedyPort?wsdl");
            remedyService = new RemedyService(new URL(address), REMEDY_SERVICE_NAME);
        }
        catch (Exception exp) {
            TraceableException.writeLog(ErrorCode.COMM_ERROR, "failed to init RemedyService:", exp);
        }

        mapper = new ObjectMapper(new XmlFactory());
        mapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);

        // subscribe some MQ topic:

        // schedule attachment sync task
        String itsmCode = params.getAttribute(AItsmAdapter.ADAPTER_ITSMCOMDE, "");
        SyncAttachmentTask syncAttachTask = new SyncAttachmentTask(itsmCode, ADaoFactory.getDefault(), this);
        ScheduleTaskManager.getDefaultSchduler().scheduleTask(syncAttachTask);

        // start AsyncUpdateTicket queue, cache failed command up to 2 hours
        cacher = CacheFactory.getGlobalCache(params.getAttribute(ParamNameDef.CACHE_SERVER_NODE, "default"));
        
        RetrySyncTicketTask syncTicketTask = new RetrySyncTicketTask(itsmCode, cacher, this);
        ScheduleTaskManager.getDefaultSchduler().scheduleTask(syncTicketTask);
        
        // create base data importer for import project, sp, client, and so on data.
        importer = new BaseDataImporter(itsmCode, ARepositoryFactory.getFactory());
    }

    @Override
    public void onMessage(String topic, Object content) {
    }

    @SuppressWarnings("unchecked")
    @Override
    public Pair<Boolean, String> authenticate(String loginName, String password) {
        try {
            MainWebServices remedyPort = remedyService.getRemedyPort();
            Map<String, String> request = new HashMap<>();
            request.put("ChrLoginName", loginName);
            request.put("ChrPassword", Base64.getEncoder().encodeToString(password.getBytes()));

            String response = remedyPort.validateUser(wrapToXml(request));
            request.clear();

            if (logger.isDebugEnabled())
                logger.debug(response);

            // contain two xml node in response, status and content
            Map<String, Object> obj = mapper.readValue(response, Map.class);

            // check status, if not OK, get reson from content xml node
            if (obj.get(REMEDY_FIELD_STATUS).equals(ResponseCode.OK.name().toLowerCase()))
                return Tuple.makePair(true, ResponseCode.OK.name());
            else
                return Tuple.makePair(false, obj.get(REMEDY_FIELD_CONTENT).toString());
        }
        catch (Exception exp) {
            return Tuple.makePair(false, "Exception:" + exp.getMessage());
        }
    }

    @Override
    public Pair<Boolean, String> resetPassword(EngineerVO engineer) {
        if (Objects.isNull(engineer) || CommonUtil.isNullOrEmpty(engineer.getLoginname()))
            return Tuple.makePair(false, ErrorCode.COMM_PARAM_MISSED.getReason());

        try {
            MainWebServices remedyPort = remedyService.getRemedyPort();
            Map<String, String> request = new HashMap<>();
            // change engineer password to default value "lenovoms"
            request.put("ChrLoginName", engineer.getLoginname());
            request.put("ChrPassword", Base64.getEncoder().encodeToString("lenovoms".getBytes()));
            String response = remedyPort.updateUserPass(wrapToXml(request));

            request.clear();
            if (logger.isDebugEnabled())
                logger.debug(response);

            // contain two xml node in response, status and content
            @SuppressWarnings("unchecked")
            Map<String, Object> obj = mapper.readValue(response, Map.class);

            // check status, if not OK, get reson from content xml node
            if (obj.get(REMEDY_FIELD_STATUS).equals(ResponseCode.OK.name().toLowerCase()))
                return Tuple.makePair(true, ResponseCode.OK.name());
            else
                return Tuple.makePair(false, obj.get(REMEDY_FIELD_CONTENT).toString());
        }
        catch (Exception exp) {
            return Tuple.makePair(false, "Exception:" + exp.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    public Tuple.Triplet<Boolean, String, EngineerPO> getEngineer(String loginName) {
        try {
            MainWebServices remedyPort = remedyService.getRemedyPort();
            Map<String, String> request = new HashMap<>();
            request.put("ChrLoginName", loginName);

            String response = remedyPort.engineerInfo(wrapToXml(request));
            request.clear();

            if (logger.isDebugEnabled())
                logger.debug(response);

            // contain two xml node:status and content, content node has child
            // node Info to describe engineer
            Map<String, Object> obj = mapper.readValue(response, Map.class);

            // when status is OK, content node has engineer informantion
            if (obj.get(REMEDY_FIELD_STATUS).equals(ResponseCode.OK.name().toLowerCase())) {
                LowerCaseMap<String> infos = new LowerCaseMap<>();
                ((Map<String, String>) obj.get(REMEDY_FIELD_CONTENT)).forEach((key, value) -> infos.put(key, value));
                EngineerPO engineer = new EngineerPO();
                engineer.setLoginName(loginName);
                engineer.setId(loginName);
                engineer.setEmail(infos.get("chremailaddress"));
                engineer.setPhone(infos.get("chrmobilenumber"));
                engineer.setFullName(infos.get("chrfullname"));
                engineer.setSpId(infos.get("stationid2"));
                String status = infos.get("engineerstatus");
                status = (Objects.isNull(status) || status.equals("null") ? "0" : status);
                engineer.setStatus(EngineerStatus.values()[Integer.parseInt(status)]);
                infos.clear();
                return Tuple.makeTriplet(true, obj.get(REMEDY_FIELD_CONTENT).toString(), engineer);
            }
            else
                return Tuple.makeTriplet(false, obj.get(REMEDY_FIELD_CONTENT).toString(), null);
        }
        catch (Exception exp) {
            return Tuple.makeTriplet(false, "Exception:" + exp.getMessage(), null);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public Triplet<Boolean, String, TicketPO> getTicket(String ticketID) {
        try {
            MainWebServices remedyPort = remedyService.getRemedyPort();
            Map<String, String> request = new HashMap<>();
            request.put("ticketId", ticketID);
            String response = remedyPort.ticketInfo(wrapToXml(request));

            request.clear();
            if (logger.isDebugEnabled())
                logger.debug(response);

            // contain two xml node in response, status and content
            Map<String, Object> obj = mapper.readValue(response, Map.class);

            // when status is OK, content node has engineer informantion
            if (obj.get(REMEDY_FIELD_STATUS).equals(ResponseCode.OK.name().toLowerCase())) {
                LowerCaseMap<String> infos = new LowerCaseMap<>();
                ((Map<String, String>) obj.get(REMEDY_FIELD_CONTENT)).forEach((key, value) -> infos.put(key, value));
                TicketPO po = syncHandler.toTicketPO(infos);

                return Tuple.makeTriplet(true, obj.get(REMEDY_FIELD_CONTENT).toString(), po);
            }
            else
                return Tuple.makeTriplet(false, obj.get(REMEDY_FIELD_CONTENT).toString(), null);
        }
        catch (Exception exp) {
            return Tuple.makeTriplet(false, "Exception:" + exp.getMessage(), null);
        }
    }

    @Override
    public Pair<Boolean, String> updateTicket(ChangedTicketVO ticket) {
        preparaParameter(ticket);
        Map<String, String> request = new HashMap<>();
        request.put("ticketID", ticket.getTicketid());
        request.put("OperationTime", CommonUtil.getDateFormatter("yyyy-MM-dd HH:mm:ss").format(new Date()));

        // base on action to make request, some field needn't send to itsm
        for (Map.Entry<String, String> entry : ticket.getFields().entrySet())
            request.put(entry.getKey(), entry.getValue());

        String data = wrapToXml(request);
        request.clear();

        Tuple.Pair<Boolean, String> result = updateTicket(data);

        // when sync ticket meet any error, put request to cache queue, let
        // backgroud thread to retry
        if (result.getOne())
            return result;
        else {
            String itsmCode = getParameters().getAttribute(AItsmAdapter.ADAPTER_ITSMCOMDE, "");
            logger.warn("sync ticket data to {} meet error {}", itsmCode, result.getSecond());
            
            // put command data to cache
            CacheItem<List<String>> cachedTicket = cacher.get(itsmCode, ticket.getTicketid());
            if (Objects.isNull(cachedTicket.getValue())) {
                List<String> datas = new ArrayList<>();
                datas.add(data);
                cacher.put(new CacheItem<List<String>>(ticket.getTicketid(), datas, itsmCode, -1));
            }
            else {
                cachedTicket.getValue().add(data);
                cacher.put(cachedTicket);
            }

            // create or update ticket list in cache
            CacheItem<Set<String>> cachedIdSet = cacher.get(itsmCode, TICKET_LIST_CACHE_KEY);
            if (Objects.isNull(cachedIdSet) || Objects.isNull(cachedIdSet.getValue())) {
                Set<String> ticketSet = new HashSet<>();
                ticketSet.add(ticket.getTicketid());
                cacher.put(new CacheItem<Set<String>>(TICKET_LIST_CACHE_KEY, ticketSet, itsmCode, -1));
            }
            else {
                cachedIdSet.getValue().add(ticket.getTicketid());
                cachedIdSet.setTTL(-1);
                cacher.put(cachedIdSet);
            }

            return Tuple.makePair(true, "communicate error with remedy, cache request first");
        }
    }

    @SuppressWarnings("unchecked")
    protected Pair<Boolean, String> updateTicket(String request) {
        try {
            MainWebServices remedyPort = remedyService.getRemedyPort();
            String response = remedyPort.updateTicket(request);

            if (logger.isDebugEnabled())
                logger.debug(response);

            // contain two xml node in response, status and content
            Map<String, Object> obj = mapper.readValue(response, Map.class);

            // check status, if not OK, get reson from content xml node
            if (obj.get(REMEDY_FIELD_STATUS).equals(ResponseCode.OK.name().toLowerCase()))
                return Tuple.makePair(true, ResponseCode.OK.name());
            else
                return Tuple.makePair(false, obj.get(REMEDY_FIELD_CONTENT).toString());
        }
        catch (Exception exp) {
            return Tuple.makePair(false, "Exception:" + exp.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public Pair<Boolean, String> updateEngineer(EngineerVO engineer) {
        if (Objects.isNull(engineer) || CommonUtil.isNullOrEmpty(engineer.getLoginname()))
            return Tuple.makePair(false, ErrorCode.COMM_PARAM_MISSED.getReason());

        try {
            MainWebServices remedyPort = remedyService.getRemedyPort();
            Map<String, String> request = new HashMap<>();
            String response = "";
            if (CommonUtil.nonNullAndEmpty(engineer.getPasswd())) {
                // change engineer password;
                request.put("ChrLoginName", engineer.getLoginname());
                request.put("ChrPassword", Base64.getEncoder().encodeToString(engineer.getPasswd().getBytes()));
                response = remedyPort.updateUserPass(wrapToXml(request));

                if (logger.isDebugEnabled())
                    logger.debug(response);
            }

            // change engineer mail, phone and status, if mail, phone is empty,
            // don't submit to ITSM
            request.clear();
            request.put("ChrLoginName", engineer.getLoginname());

            if (CommonUtil.nonNullAndEmpty(engineer.getEmail()))
                request.put("ChrEmailAddress", engineer.getEmail());

            if (CommonUtil.nonNullAndEmpty(engineer.getPhone()))
                request.put("ChrMobileNumber", engineer.getPhone());

            request.put("EngineerStatus", String.valueOf(engineer.getStatus()));
            response = remedyPort.engneerStatus(wrapToXml(request));

            if (logger.isDebugEnabled())
                logger.debug(response);

            Map<String, String> obj = mapper.readValue(response, Map.class);

            if (obj.get(REMEDY_FIELD_STATUS).equals(ResponseCode.OK.name().toLowerCase()))
                return Tuple.makePair(true, ResponseCode.OK.name());
            else
                return Tuple.makePair(false, obj.get(REMEDY_FIELD_CONTENT));
        }
        catch (Exception exp) {
            return Tuple.makeTriplet(false, "Exception:" + exp.getMessage(), null);
        }
    }

    @Override
    public Pair<Boolean, String> uploadTicketAttachment(AttachmentPO attachment) {
        try {
            MainWebServices remedyPort = remedyService.getRemedyPort();
            Map<String, String> request = new HashMap<>();

            // read file from local file system or FTP server
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            fileService.readFile(attachment.getStorePath(), output);
            output.flush();

            // suggest to limit file size, because the performance is low by
            // using SOAP and base64 to transmit file
            request.put("TICKETID", attachment.getTicketId());
            request.put("FILENAME", attachment.getFileName());
            request.put("FILETYPE", String.valueOf(attachment.getType().ordinal()));
            request.put("SUBMITTERNAME", attachment.getSubmitter());
            request.put("STEPNAME", "服务实施");// attachment.getTag());
            request.put("ATTACH", Base64.getEncoder().encodeToString(output.toByteArray()));

            String response = remedyPort.attachmentInfo(wrapToXml(request));
            request.clear();
            output.close();

            if (logger.isDebugEnabled())
                logger.debug(response);

            // contain two xml node in response, status and content
            @SuppressWarnings("unchecked")
            Map<String, Object> obj = mapper.readValue(response, Map.class);

            // check status, if not OK, get reson from content xml node
            if (obj.get(REMEDY_FIELD_STATUS).equals(ResponseCode.OK.name().toLowerCase()))
                return Tuple.makePair(true, ResponseCode.OK.name());
            else
                return Tuple.makePair(false, obj.get(REMEDY_FIELD_CONTENT).toString());
        }
        catch (Exception exp) {
            logger.warn("", exp);
            return Tuple.makePair(false, "Exception:" + exp.getMessage());
        }
    }

    @Override
    public Triplet<Boolean, String, TicketPO> createTicket(TicketVO ticket) {
        try {
            MainWebServices remedyPort = remedyService.getRemedyPort();
            Map<String, String> request = new HashMap<>();

            // create inspection ticket, this ticket will be closed in itsm
            // after created
            request.put("Summary", ticket.getTitle());
            request.put("provinceName", ticket.getOthers().get("provinceName"));
            request.put("CityName", ticket.getOthers().get("CityName"));
            if (Objects.nonNull(ticket.getOthers().get("County")))
                request.put("County", ticket.getOthers().get("County"));
            request.put("submiterFullname", ticket.getAssignee());
            request.put("ProjectID", ticket.getProject());
            request.put("ProjectName", ticket.getOthers().get("projectName"));
            request.put("ChrUserID", ticket.getOthers().get("ChrUserID"));
            request.put("customerName", ticket.getOthers().get("customerName"));
            request.put("customeraddress", ticket.getOthers().get("customeraddress"));
            request.put("customerphone", ticket.getOthers().get("customerphone"));

            String response = remedyPort.createTicket(wrapToXml(request));
            request.clear();

            if (logger.isDebugEnabled())
                logger.debug(response);

            // contain two xml node in response, status and content
            @SuppressWarnings("unchecked")
            Map<String, Object> obj = mapper.readValue(response, Map.class);

            // check status, if not OK, get reson from content xml node
            if (obj.get(REMEDY_FIELD_STATUS).equals(ResponseCode.OK.name().toLowerCase())) {
                ticket.setTicketid((String) obj.get(REMEDY_FIELD_CONTENT));
                TicketPO po = new TicketPO();
                po.setId(ticket.getTicketid());
                po.setStatus(4); // ticket closed
                po.setPolicy(0);
                po.setProjectId(ticket.getProject());
                po.setAssignee(ticket.getAssignee());
                po.setCreateTime(new Date());
                po.setCaption(ticket.getTitle());
                po.setUpdateTime(po.getCreateTime());
                po.setCreater(ticket.getOthers().get("submiterFullname"));
                po.setCustomerId(ticket.getOthers().get("ChrUserID"));
                
                // check ticket extension info
                if (Objects.nonNull(ticket.getOthers())) {
                    TicketExtensePO expo = new TicketExtensePO();
                    expo.setId(ticket.getTicketid());
                    for (Map.Entry<String, String> entry : ticket.getOthers().entrySet()) {
                        expo.getAttributeBean().addAttribute(entry.getKey(), entry.getValue());
                    }
                    expo.getAttributeBean().addAttribute("address", ticket.getOthers().get("customeraddress"));
                    expo.setExtprops(expo.getAttributeBean().dump());
                    po.setOthers(expo);
                }
                return Tuple.makeTriplet(true, ResponseCode.OK.name(), po);
            }
            else
                return Tuple.makeTriplet(false, obj.get(REMEDY_FIELD_CONTENT).toString(), null);
        }
        catch (Exception exp) {
            return Tuple.makeTriplet(false, "Exception:" + exp.getMessage(), null);
        }
    }

    @Override
    public Pair<Boolean, String> replaceParts(String ticketID, List<PartsVO> partsList) {
        try {
            MainWebServices remedyPort = remedyService.getRemedyPort();
            for (PartsVO vo : partsList) {
                Map<String, String> request = new HashMap<>();

                request.put("FRUNO", vo.getFru());
                request.put("TICKETID", ticketID);
                request.put("PARTSNUMBER", vo.getPartsnum());
                request.put("PARTSNAME", vo.getPartsname());
                // 0 - new parts, 1 - old parts
                request.put("PartsType", vo.isNew() ? "0" : "1");
                request.put("PartNote", vo.getDescription());

                String response = remedyPort.partsInfo(wrapToXml(request));
                request.clear();

                if (logger.isDebugEnabled())
                    logger.debug(response);

                // contain two xml node in response, status and content
                @SuppressWarnings("unchecked")
                Map<String, Object> obj = mapper.readValue(response, Map.class);

                // check status, if not OK, get reson from content xml node
                if (obj.get(REMEDY_FIELD_STATUS).equals(ResponseCode.OK.name().toLowerCase()))
                    continue;
                else
                    return Tuple.makePair(false, obj.get(REMEDY_FIELD_CONTENT).toString());
            }
            return Tuple.makePair(true, ResponseCode.OK.name());
        }
        catch (Exception exp) {
            return Tuple.makePair(false, "Exception:" + exp.getMessage());
        }
    }

    public String getAvaliableIP() {
        try {
            Enumeration<NetworkInterface> nics = NetworkInterface.getNetworkInterfaces();
            while (nics.hasMoreElements()) {
                NetworkInterface nic = nics.nextElement();
                if (nic.isLoopback() == false && nic.isPointToPoint() == false && nic.isUp()) {
                    Enumeration<InetAddress> addrs = nic.getInetAddresses();
                    while (addrs.hasMoreElements()) {
                        InetAddress addr = addrs.nextElement();
                        if (addr instanceof Inet4Address)
                            return addr.getHostAddress();
                    }
                }
            }
            return null;
        }
        catch (Exception exp) {
            logger.error("", exp);
            return "127.0.0.1";
        }
    }

    /**
     * We use StringBuild to make XML document because all data field in CDATA node special character need to deal with  
     * 
     * @param params
     * @return
     */
    private String wrapToXml(Map<String, String> params) {
        StringBuilder sb = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
        sb.append("<Info>\r\n");
        for (Map.Entry<String, String> entry : params.entrySet()) {
            sb.append("<").append(entry.getKey()).append("><![CDATA[").append(entry.getValue()).append("]]></")
                .append(entry.getKey()).append(">\r\n");
        }
        sb.append("</Info>\r\n");

        if (logger.isDebugEnabled() && sb.length() < 4096) // don't log big attachment soap request
            logger.debug(sb.toString());
        return sb.toString();
    }

    @Override
    public Pair<Boolean, String> importEntityData(EntityName entity, InputStream input) {
        try {
            if (entity.equals(EntityName.PROJECTENGINEER))
                importer.importProjectEngineer(input);

            return Tuple.makePair(true, ResponseCode.OK.name());
        }
        catch (Exception exp) {
            return Tuple.makePair(false, "Exception:" + exp.getMessage());
        }
    }

    /**
     * 准备同步的参数
     * 
     * @param ticket
     */
    private void preparaParameter(ChangedTicketVO ticket) {
        TicketAction actionEnum = TicketAction.get(ticket.getAction().toUpperCase());
        if (Objects.isNull(actionEnum))
            return;
        
        Map<String, String> fields = ticket.getFields();
        if (Objects.isNull(fields)) {
            fields = new HashMap<>();
            ticket.setFields(fields);
        }
        
        switch (actionEnum) {
            case CONFIRM:
                prepareConfirm(ticket, fields);
                break;
            case APPOINT:
                prepareAppoint(ticket, fields);
                break;
            case SIGNIN:
                prepareSignIn(ticket, fields);
                break;
            case CLOSE:
                prepareClose(ticket, fields);
                break;
            case FINISH:
                fields.put("actualcompletetime", CommonUtil.getDateFormatter("yyyy-MM-dd HH:mm:ss").format(new Date()));
                break;
            case GRAB:
                break;
            case REJECT:
                break;
            case REAPPOINT:
                createAddress(ticket,fields);
                break;
            case RECONFIRM:
                break;
            case PICKPARTS:
                break;
            case ATTACH:
                break;
            case EVALUATE:
                break;
            case BREAKOFF:
                break;
            default:
                break;
        }
    }

    /**
     * 
     * @param ticket
     * @param fields
     */
    private void prepareSignIn(ChangedTicketVO ticket, Map<String, String> fields) {
        // 上门时间回传itsm
        fields.put("gettotime", CommonUtil.getDateFormatter("yyyy-MM-dd HH:mm:ss").format(new Date()));
        // 签到类型 0 自动 ,1 手动
        String signtype = fields.get("signtype");
        if (CommonUtil.isNullOrEmpty(signtype)) {
            fields.put("signtype", "1");
        }
        ticket.setFields(fields);
    }

    /**
     * 
     * @param ticket
     * @param fields
     */
    private void prepareClose(ChangedTicketVO ticket, Map<String, String> fields) {
        try (ICommonDAO dao = ADaoFactory.getDefault().createDAO(
            getParameters().getAttribute(AItsmAdapter.ADAPTER_ITSMCOMDE, ""))) {
            
            String hql = "from EngineerPO where loginName in (select assignee from TicketPO where id =?)";
            EngineerPO engineerPO = dao.getRegisteredFinder(FinderEnum.HQL).getSingleRecord(
                    hql, EngineerPO.class, new String[] { ticket.getTicketid() });
            
            fields.put("assigneeloginname", engineerPO.getLoginName());
            fields.put("assigneefullname", engineerPO.getFullName());
        }
        catch (Exception e) {
            throw new TraceableException(ErrorCode.DB_QUERY_FAILED, "ticket " + ticket.getTicketid(), e);
        }
    }

    /**
     * 
     * @param ticket
     * @param fields
     */
    private void prepareConfirm(ChangedTicketVO ticket, Map<String, String> fields) {
        try (ICommonDAO dao = ADaoFactory.getDefault().createDAO(
            getParameters().getAttribute(AItsmAdapter.ADAPTER_ITSMCOMDE, ""))) {
            // 获取工程师信息， 回传itsm系统
            String engineer = ticket.getFields().get("assigneeloginname");
            String hql = "from EngineerPO where loginName = ?";
            EngineerPO engineerPO = dao.getRegisteredFinder(FinderEnum.HQL).getSingleRecord(
                    hql,EngineerPO.class, new String[] { engineer });
            
            fields.put("assigneefullname", engineerPO.getFullName());
            fields.put("pickuptime", CommonUtil.getDateFormatter("yyyy-MM-dd HH:mm:ss").format(new Date()));
        }
        catch (Exception e) {
            throw new TraceableException(ErrorCode.DB_QUERY_FAILED, e);
        }
    }

    /**
     * 
     * @param ticket
     * @param fields
     */
    private void prepareAppoint(ChangedTicketVO ticket, Map<String, String> fields) {
        fields.put("datetime", CommonUtil.getDateFormatter("yyyy-MM-dd HH:mm:ss").format(new Date()));
        createAddress(ticket, fields);
    }
    /**
     * 保存Address信息
     * @param ticket
     * @param fields
     */
    private void createAddress(ChangedTicketVO ticket, Map<String, String> fields) {
        
        String aid = fields.get("address");
        String latitude = fields.remove("latitude");
        String longitude = fields.remove("longitude");
        
        if (CommonUtil.isNullOrEmpty(aid))
            return;
        
        try (ICommonDAO dao = ADaoFactory.getDefault().createDAO(
            getParameters().getAttribute(AItsmAdapter.ADAPTER_ITSMCOMDE, ""))) {
            
            aid = String.valueOf(aid.hashCode());
            AddressPO address = dao.get(AddressPO.class, aid);
            if (Objects.isNull(address)) {
                address = new AddressPO();
                address.setId(aid);
                address.setAddress(fields.get("address"));
                
                if (CommonUtil.nonNullAndEmpty(latitude) && CommonUtil.nonNullAndEmpty(longitude)) { 
                    address.setLatitude(Double.valueOf(latitude));
                    address.setLongitude(Double.valueOf(longitude));
                    dao.beginTrans();
                    dao.save(address);
                    dao.endTrans();
                }
            }
        }
        catch (Exception exp) {
            logger.warn("save address meet error", exp);
        }
    }
}
